﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Vortex.Drawing;
using FarseerPhysics.Dynamics;
using FarseerPhysics.Collision.Shapes;
using FarseerPhysics.Common;
using FarseerPhysics.Dynamics.Joints;
using FarseerPhysics.Dynamics.Contacts;
using FarseerPhysics.Collision;

namespace FarseerPhysics {

	///<summary>Implementation of Farseer's debug view for Vortex engine</summary>
	public class VortexDebugView {

		public Canvas2D Canvas { get; private set; }
		public World World { get; private set; }

		/// <summary>
		/// Gets or sets the debug view flags.
		/// </summary>
		/// <value>The flags.</value>
		public DebugViewFlags Flags { get; set; }

		/// <summary>
		/// Append flags to the current flags.
		/// </summary>
		/// <param name="flags">The flags.</param>
		public void AppendFlags(DebugViewFlags flags) {
			Flags |= flags;
		}

		/// <summary>
		/// Remove flags from the current flags.
		/// </summary>
		/// <param name="flags">The flags.</param>
		public void RemoveFlags(DebugViewFlags flags) {
			Flags &= ~flags;
		}

		public VortexDebugView(Canvas2D canvas, World world) {
			Canvas = canvas;
			World = world;

			PointColor = ColorU.Blue;
			JointColor = new ColorU(128, 212, 212);
			PairColor = ColorU.Gray.MultiplyAlpha(.5f);
			AABBColor = ColorU.Aqua;
			InactiveShapeColor = ColorU.Gray;
			StaticShapeColor = ColorU.DarkGray;
			KinematicShapeColor = ColorU.White;
			SleepingShapeColor = ColorU.LightGray;
			DefaultShapeColor = ColorU.Wheat;
		}

		public ColorU PointColor { get; set; }
		public ColorU JointColor { get; set; }
		public ColorU PairColor { get; set; }
		public ColorU AABBColor {get; set; }
		public ColorU InactiveShapeColor { get; set; }
		public ColorU StaticShapeColor { get; set; }
		public ColorU KinematicShapeColor { get; set; }
		public ColorU SleepingShapeColor { get; set; }
		public ColorU DefaultShapeColor { get; set; }

		public void DrawPolygon(Vector2[] vertices, int count, ColorU color) {
			Canvas.DrawPolygon(vertices, 0, count, color);
		}

		public void DrawSolidPolygon(Vector2[] vertices, int count, ColorU color) {
			Canvas.DrawPolygon(vertices, 0, count, color);
		}

		public void DrawCircle(Vector2 center, float radius, ColorU color) {
			Canvas.DrawEllipse(Rect.FromPoint(center, new Vector2(radius * 2)), color);
		}

		public void DrawSolidCircle(Vector2 center, float radius, Vector2 axis, ColorU color) {
			Canvas.DrawFilledEllipse(Rect.FromPoint(center, new Vector2(radius * 2)), color);
		}

		public void DrawSegment(Vector2 start, Vector2 end, ColorU color) {
			Canvas.DrawLine(start, end, color);
		}

		public void DrawPoint(Vector2 point, float size, ColorU color) {
			Canvas.DrawRect(Rect.FromPoint(point.Round(), new Vector2(size) + 1), color);
		}

		public void DrawTransform(ref FarseerPhysics.Common.Transform transform) {
			//Canvas.DrawLine(start, end, new Color4F(red, green, blue).ToColorU());
		}

		public void Draw() {
			if ((Flags & DebugViewFlags.Shape) == DebugViewFlags.Shape) {
				foreach (Body b in World.BodyList) {
					Transform xf;
					b.GetTransform(out xf);
					foreach (Fixture f in b.FixtureList) {
						if (b.Active == false) {
							DrawShape(f, xf, InactiveShapeColor);
						} else {
							if (b.BodyType == BodyType.Static) {
								DrawShape(f, xf, StaticShapeColor);
							} else if (b.BodyType == BodyType.Kinematic) {
								DrawShape(f, xf, KinematicShapeColor);
							} else if (b.Awake == false) {
								DrawShape(f, xf, SleepingShapeColor);
							} else {
								DrawShape(f, xf, DefaultShapeColor);
							}
						}
					}
				}
			}

			if ((Flags & DebugViewFlags.PolygonPoints) == DebugViewFlags.PolygonPoints) {
				foreach (Body body in World.BodyList) {
					foreach (Fixture f in body.FixtureList) {
						PolygonShape polygon = f.Shape as PolygonShape;
						if (polygon != null) {
							Transform xf;
							body.GetTransform(out xf);

							for (int i = 0; i < polygon.Vertices.Count; i++) {
								Vector2 tmp = MathUtils.Multiply(ref xf, polygon.Vertices[i]);
								DrawPoint(tmp, 3, PointColor);
							}
						}
					}
				}
			}

			if ((Flags & DebugViewFlags.Joint) == DebugViewFlags.Joint) {
				foreach (Joint j in World.JointList) {
					DrawJoint(j);
				}
			}

			if ((Flags & DebugViewFlags.Pair) == DebugViewFlags.Pair) {
				for (Contact c = World.ContactManager.ContactList; c != null; c = c.Next) {
					Fixture fixtureA = c.FixtureA;
					Fixture fixtureB = c.FixtureB;

					AABB aabbA;
					fixtureA.GetAABB(out aabbA, 0);
					AABB aabbB;
					fixtureB.GetAABB(out aabbB, 0);

					Vector2 cA = aabbA.Center;
					Vector2 cB = aabbB.Center;

					DrawSegment(cA, cB, PairColor);
				}
			}

			if ((Flags & DebugViewFlags.AABB) == DebugViewFlags.AABB) {
				ColorU color = AABBColor;
				BroadPhase bp = World.ContactManager.BroadPhase;

				foreach (Body b in World.BodyList) {
					if (b.Active == false) {
						continue;
					}

					foreach (Fixture f in b.FixtureList) {
						for (int t = 0; t < f.ProxyCount; ++t) {
							FixtureProxy proxy = f.Proxies[t];
							AABB aabb;
							bp.GetFatAABB(proxy.ProxyId, out aabb);
							Vector2[] vs = new Vector2[4];
							vs[0] = new Vector2(aabb.LowerBound.X, aabb.LowerBound.Y);
							vs[1] = new Vector2(aabb.UpperBound.X, aabb.LowerBound.Y);
							vs[2] = new Vector2(aabb.UpperBound.X, aabb.UpperBound.Y);
							vs[3] = new Vector2(aabb.LowerBound.X, aabb.UpperBound.Y);

							DrawPolygon(vs, 4, color);
						}
					}
				}
			}

			if ((Flags & DebugViewFlags.CenterOfMass) == DebugViewFlags.CenterOfMass) {
				foreach (Body b in World.BodyList) {
					Transform xf;
					b.GetTransform(out xf);
					xf.Position = b.WorldCenter;
					DrawTransform(ref xf);
				}
			}

			if ((Flags & DebugViewFlags.DebugPanel) == DebugViewFlags.DebugPanel) {
				DrawDebugPanel();
			}
		}

		private void DrawShape(Fixture fixture, Transform xf, ColorU color) {
			switch (fixture.ShapeType) {
				case ShapeType.Circle: {
						CircleShape circle = (CircleShape)fixture.Shape;

						Vector2 center = MathUtils.Multiply(ref xf, circle.Position);
						float radius = circle.Radius;
						Vector2 axis = xf.R.col1;

						DrawSolidCircle(center, radius, axis, color);
						//axis,
					}
					break;

				case ShapeType.Polygon: {
						PolygonShape poly = (PolygonShape)fixture.Shape;
						int vertexCount = poly.Vertices.Count;
						Vector2[] vertices = new Vector2[Settings.MaxPolygonVertices];

						for (int i = 0; i < vertexCount; ++i) {
							vertices[i] = MathUtils.Multiply(ref xf, poly.Vertices[i]);
						}

						DrawSolidPolygon(vertices, vertexCount, color);
					}
					break;


				case ShapeType.Edge: {
						EdgeShape edge = (EdgeShape)fixture.Shape;
						Vector2 v1 = MathUtils.Multiply(ref xf, edge.Vertex1);
						Vector2 v2 = MathUtils.Multiply(ref xf, edge.Vertex2);
						DrawSegment(v1, v2, color);
					}
					break;

				case ShapeType.Loop: {
						LoopShape loop = (LoopShape)fixture.Shape;
						int count = loop.Count;

						Vector2 v1 = MathUtils.Multiply(ref xf, loop.Vertices[count - 1]);
						for (int i = 0; i < count; ++i) {
							Vector2 v2 = MathUtils.Multiply(ref xf, loop.Vertices[i]);
							DrawSegment(v1, v2, color);
							v1 = v2;
						}
					}
					break;
			}
		}

		private void DrawJoint(Joint joint) {
			Body b1 = joint.BodyA;
			Body b2 = joint.BodyB;
			Transform xf1, xf2;
			b1.GetTransform(out xf1);

			Vector2 x2 = new Vector2();

			// WIP David
			if (!joint.IsFixedType()) {
				b2.GetTransform(out xf2);
				x2 = xf2.Position;
			}
			Vector2 p2 = joint.WorldAnchorB;
			Vector2 x1 = xf1.Position;
			Vector2 p1 = joint.WorldAnchorA;
			ColorU color = JointColor;

			switch (joint.JointType) {
				case JointType.Distance:
					DrawSegment(p1, p2, color);
					break;

				case JointType.Pulley: {
						PulleyJoint pulley = (PulleyJoint)joint;
						Vector2 s1 = pulley.GroundAnchorA;
						Vector2 s2 = pulley.GroundAnchorB;
						DrawSegment(s1, p1, color);
						DrawSegment(s2, p2, color);
						DrawSegment(s1, s2, color);
					}
					break;

				case JointType.FixedMouse:

					FixedMouseJoint fixedMouseJoint = (FixedMouseJoint)joint;
					p1 = fixedMouseJoint.Target;

					DrawPoint(p2, 0.5f, ColorU.Green);
					DrawSegment(p1, p2, ColorU.LightGray);
					break;
				case JointType.Revolute:
					//DrawSegment(x2, p1, color);
					DrawSegment(p2, p1, color);
					DrawSolidCircle(p2, 0.1f, new Vector2(), ColorU.Red);
					DrawSolidCircle(p1, 0.1f, new Vector2(), ColorU.Blue);
					break;
				case JointType.FixedRevolute:
					DrawSolidCircle(p1, 0.1f, new Vector2(), ColorU.Pink);
					break;
				case JointType.FixedLine:
					DrawSegment(x1, p1, color);
					DrawSegment(p1, p2, color);
					break;
				case JointType.FixedDistance:
					DrawSegment(x1, p1, color);
					DrawSegment(p1, p2, color);
					break;
				case JointType.FixedPrismatic:
					DrawSegment(x1, p1, color);
					DrawSegment(p1, p2, color);
					break;
				case JointType.Gear:
					DrawSegment(x1, x2, color);
					//DrawSegment(x1, p1, color);
					//DrawSegment(p1, p2, color);
					break;
				default:
					DrawSegment(x1, p1, color);
					DrawSegment(p1, p2, color);
					DrawSegment(x2, p2, color);
					break;
			}
		}

		private void DrawDebugPanel() {
			Canvas.PushTransform();
			Canvas.ResetTransform();

			StringBuilder sb = new StringBuilder();

			sb.AppendFormat("Bodies: {0}\n\n", World.BodyList.Count);

			//TODO: Is the number of contacts reliable?
			sb.AppendFormat("Contacts: {0}\n", World.ContactCount);
			sb.AppendFormat("Joints: {0}\n", World.JointList.Count);
			sb.AppendFormat("Proxies: {0}\n", World.ProxyCount);
			sb.AppendFormat("Breakable: {0}\n", World.BreakableBodyList.Count);
			sb.AppendFormat("Controllers: {0}\n\n", World.Controllers.Count);

			sb.AppendFormat("New contacts: {0}\n", World.NewContactsTime);
			sb.AppendFormat("Controllers: {0}\n", World.ControllersUpdateTime);
			sb.AppendFormat("Contacts: {0}\n", World.ContactsUpdateTime);
			sb.AppendFormat("Solve: {0}\n", World.SolveUpdateTime);
			sb.AppendFormat("CCD: {0}\n", World.ContinuousPhysicsTime);
			sb.AppendFormat("Total: {0}\n", World.UpdateTime);

			Canvas.DrawTextLayout(SpriteFont.SmallConsole, Rect.FromBox(20, 32, 300, 300), sb.ToString(), TextLayout.Default, ColorU.White.MultiplyAlpha(0.5f));

			Canvas.PopTransform();
		}
	}
}
